package com.dg.gaming.api.common.util;

import javax.crypto.SecretKeyFactory;
import java.security.SecureRandom;
import java.security.*;
import nanoxml.*;
import java.lang.reflect.*;
import javax.crypto.SecretKey;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import com.dg.gaming.api.common.msg.*;
import javax.crypto.*;
import java.security.spec.*;

public class DigestGenerator {
    static private final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6',
                                         '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
                                         'F'};
    public static byte[] decodeHex(String data) {

        int len = data.length();

        if ((len & 0x01) != 0) {
            throw new RuntimeException("Odd number of characters.");
        }

        byte[] out = new byte[len>>1];

        // two characters form the hex value.
        for (int i = 0, j = 0; j < len; i++) {
            int f = toDigit(data.charAt(j), j) << 4;
            j++;
            f = f | toDigit(data.charAt(j), j);
            j++;
            out[i] = (byte) (f & 0xFF);
        }

        return out;
    }

    protected static int toDigit(char ch, int index) {
        int digit = Character.digit(ch, 16);
        if (digit == -1) {
            throw new RuntimeException("Illegal hexadecimal charcter " + ch +
                                       " at index " + index);
        }
        return digit;
    }

    public static String encodeHex(byte[] data) {

        int l = data.length;

        StringBuffer buffer = new StringBuffer(data.length * 2);

        // two characters form the hex value.
        for (int i = 0, j = 0; i < l; i++) {
            buffer.append(DIGITS[(0xF0 & data[i]) >>> 4]).append(DIGITS[0x0F &
                    data[i]]);
        }

        return buffer.toString();
    }

    static private final byte[] iv = new byte[] {(byte) 0x8E, 0x12, 0x39,
                                     (byte) 0x9C, 0x07, 0x72, 0x6F, 0x5A};
    private static final String XML_ATT_KEY = "DGKey";

    private SecureRandom random = null;

    private byte[] fourBytes = new byte[4];
    private byte[] twentyfourBytes = new byte[24];
    public static final byte[] generateBytes() {
        byte[] twentyfourBytes = new byte[24];
        try {
            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
            random.nextBytes(twentyfourBytes);
        } catch (NoSuchAlgorithmException ex) {
        }
        return twentyfourBytes;
    }

    public DigestGenerator() {
        random();
        random.nextBytes(twentyfourBytes);
    }

    private void random() {
        try {
            random = SecureRandom.getInstance("SHA1PRNG");
        } catch (NoSuchAlgorithmException ex) {
        }
    }

    private void copy() {
        System.arraycopy(twentyfourBytes, 10, fourBytes, 0, 4);
    }

    private void put() {
        System.arraycopy(fourBytes, 0, twentyfourBytes, 10, 4);
    }


    public DigestGenerator(byte[] twentyfourBytes) {
        random();
        if (twentyfourBytes == null || twentyfourBytes.length != 24) {
            throw new RuntimeException("The bytes size must be 24!!!");
        }

        this.twentyfourBytes = twentyfourBytes;
        copy();
    }

    private static final ThreadLocal<SecretKeyFactory> keyfactory = new
            ThreadLocal<SecretKeyFactory>() {
        protected SecretKeyFactory initialValue() {
            SecretKeyFactory keyfactory = null;
            try {
                keyfactory = SecretKeyFactory.getInstance(
                        EncryptionAlgorithm.DESede.name());
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            return keyfactory;
        }
    };

    public enum CheckSumAlgorithm {
        Adler32, CRC32
    }


    public enum MessageDigestAlgorithm {
        MD5, SHA
    }


    /**
     * based on sun link
     * https://java.sun.com/j2se/1.4.2/docs/guide/security/jce/JCERefGuide.html#AppA
     * DESede is Tripple DES algorithm
     *
     * @author xakn1
     *
     */
    public enum EncryptionAlgorithm {
        DES, SHA, DESede, RC2, RC4, RC5
    }


    public enum EncryptionMode {
        NONE, CBC, CFB, ECB, OFB, PCBC
    }


    public enum EncryptionPadding {
        NoPadding, PKCS5Padding, SSL3Padding
    }


    /**
     * hexEncode is create String object which contains hex representation of
     * data present in bytes array
     *
     * @param bytes -
     *            input array of bytes to be hex coded
     * @return String objecting containing hex representation of bytes in input
     *         array
     */


    public byte[] encrypt(byte[] clearText) throws
            Exception {

        byte[] ciphertext = null;
        try {
            Cipher cipher = chiper();
            cipher.init(Cipher.ENCRYPT_MODE, desKey(), ivVector());
            ciphertext = cipher.doFinal(clearText);
        } catch (Exception e) {
            System.err.println("Exception while encrypting data:" + clearText +
                               " Exception:" + e);
            throw new RuntimeException(e);
        }
        return ciphertext;
    }

    private SecretKey desKey() throws InvalidKeySpecException,
            InvalidKeyException {
        DESedeKeySpec keyspec = new DESedeKeySpec(twentyfourBytes);
        SecretKey deskey = keyfactory.get().generateSecret(keyspec);
        return deskey;
    }

    public byte[] decrypt(byte[] cipherText) throws
            Exception {
        byte[] cleartext = null;
        try {

            Cipher cipher = chiper();
            cipher.init(Cipher.DECRYPT_MODE, desKey(), ivVector());
            cleartext = cipher.doFinal(cipherText);
        } catch (Exception e) {
//            System.err.println("Exception while decrypting data:" +
//                               hexEncode(cipherText) + " Exception:" + e);
            throw new RuntimeException(e);
        }
        return cleartext;
    }

    private IvParameterSpec ivVector() {
        IvParameterSpec ivVector = new IvParameterSpec(iv);
        return ivVector;
    }

    public byte[] encode(XMLElement message, boolean addChiper) throws
            Exception {
        byte[] bt = null;

        random.nextBytes(this.fourBytes);
        put();
        if (addChiper) {
            message.setAttribute(XML_ATT_KEY, encodeHex(twentyfourBytes));
        }
        byte[] messageBytes = message.toString().getBytes("UTF8");
        messageBytes = encrypt(messageBytes);
        int headerSize = 1 + fourBytes.length;
        bt = new byte[headerSize + messageBytes.length];
        bt[0] = 1;
        System.arraycopy(fourBytes, 0, bt, 1,
                         fourBytes.length);
        System.arraycopy(messageBytes, 0, bt, headerSize,
                         messageBytes.length);

        return bt;
    }


    public XMLElement decode(byte[] msgBt) throws Exception {
        XMLElement message = null;
        byte[] bt = null;
        if (msgBt[0] == 1) {
            int headerSize = 1 + fourBytes.length;
            System.arraycopy(msgBt, 1, fourBytes, 0,
                             fourBytes.length);
            put();
            bt = new byte[msgBt.length - headerSize];
            System.arraycopy(msgBt, headerSize, bt, 0,
                             bt.length);
            bt = decrypt(bt);

        } else {
            bt = new byte[msgBt.length - 1];
            System.arraycopy(msgBt, 1, bt, 0, bt.length);

        }
        message = new XMLElement();
        message.parseString(new String(bt, "UTF8"));
        String key = message.getStringAttribute(XML_ATT_KEY);
        if (key != null) {
            twentyfourBytes = decodeHex(key);
        }
        return message;
    }

    private Cipher chiper() throws NoSuchPaddingException,
            NoSuchAlgorithmException {
        Cipher cipher = Cipher.getInstance(EncryptionAlgorithm.DESede.name() +
                                           "/" + EncryptionMode.CBC.name() +
                                           "/"
                                           +
                                           EncryptionPadding.PKCS5Padding.
                                           name());
        return cipher;
    }

    public static void main(String[] args) {
        try {
            DigestGenerator g = new DigestGenerator();
            MsgJoinTable jt = new MsgJoinTable(1, "dito", "1");
            XMLElement elem = jt.flatten();
            System.out.println(elem.toString());
            byte[] bt = g.encode(elem, true);
            elem = g.decode(bt);
            System.out.println(elem.toString());
        } catch (Exception ex) {
//            ex.printStackTrace();
        }
    }
}
